package com.csdn.eval.spring.mqtt.annotation;

import com.csdn.eval.spring.mqtt.core.MqttEventFactory;
import com.csdn.eval.spring.mqtt.model.MqttEventAnnotationDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * This class processes beans after their initialization to find methods annotated with
 * {@link MqttEventListener} and register them as event listeners.
 */
public class MqttEventListenerAnnotationBeanPostProcessor implements BeanPostProcessor {

    private static final Logger logger = LoggerFactory.getLogger(MqttEventListenerAnnotationBeanPostProcessor.class);

    private final MqttEventFactory mqttEventFactory;

    public MqttEventListenerAnnotationBeanPostProcessor(MqttEventFactory mqttEventFactory) {
        this.mqttEventFactory = mqttEventFactory;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
        Map<Method, Set<MqttEventListener>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
                (MethodIntrospector.MetadataLookup<Set<MqttEventListener>>) method -> {
                    Set<MqttEventListener> listenerMethods = findListenerAnnotations(method);
                    return (!listenerMethods.isEmpty() ? listenerMethods : null);
                });
        if (annotatedMethods.isEmpty()) {
            // No methods found
            logger.debug("No @MqttEventListener annotations found on bean '{}'", beanName);
        } else {
            // Non-empty set of methods
            for (Map.Entry<Method, Set<MqttEventListener>> entry : annotatedMethods.entrySet()) {
                Method method = entry.getKey();
                for (MqttEventListener listener : entry.getValue()) {
                    processMqttEventListener(listener, method, bean);
                }
            }
            logger.debug(" {} @MqttEventListener methods processed on bean '{}'"
                    , annotatedMethods.size(), beanName + ": " + annotatedMethods);
        }
        return bean;
    }

    private void processMqttEventListener(MqttEventListener listener, Method method, Object bean) {
        Class<?> eventType = listener.eventType();
        try {
            MqttEventAnnotationDefinition evalEventDefinition =
                    new MqttEventAnnotationDefinition(listener.topic(), bean.getClass(), eventType, method);
            // register evalEventDefinition
            mqttEventFactory.registerEventDefinition(evalEventDefinition);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /*
     * AnnotationUtils.getRepeatableAnnotations does not look at interfaces
     */
    private Set<MqttEventListener> findListenerAnnotations(Method method) {
        Set<MqttEventListener> listeners = new HashSet<>();
        MqttEventListener ann = AnnotatedElementUtils.findMergedAnnotation(method, MqttEventListener.class);
        if (ann != null) {
            listeners.add(ann);
        }
        return listeners;
    }

}